﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.Collections.ObjectModel;
using MediaInfoLib;
using System.Windows.Forms;

namespace MkvSplicer.Video
{
    public class MkvHandler
    {
        public long FrameCount { get { return videoTrack.FrameCount; } }
        public int Bitdepth { get { return videoTrack.Bitdepth; } }
        public string Colourspace { get { return videoTrack.Colourspace; } }
        public int VideoTrackNumber
        {
            get { return (videoTrack as MkvVideoTrack).TrackNumber; }
        }
        public string VideoInfo
        {
            get { return (videoTrack as MkvVideoTrack).VideoInfo; }
        }
        public string EncodeSettings
        {
            get { return (videoTrack as MkvVideoTrack).EncodeSettings; }
        }
        public readonly ReadOnlyCollection<SplitInfo> Splits;
        public readonly FileInfo fileIn;

        private readonly VideoTrack videoTrack;
        private List<SplitInfo> splits;

        //Throws NotsupportedException
        public MkvHandler(FileInfo fileIn, VideoTrack.UpdateProgress progressCallback, IntPtr progressCustomData)
        {
            this.fileIn = fileIn;
            this.videoTrack = new MkvVideoTrack(fileIn, progressCallback, progressCustomData);
            this.splits = new List<SplitInfo>();
            Splits = this.splits.AsReadOnly();
        }

        public void addSplit(string timeStart, string timeEnd)
        {
            splits.Add(videoTrack.parseSplit(timeStart, timeEnd));
        }

        public void addSplit(long frameStart, long frameEnd)
        {
            splits.Add(videoTrack.parseSplit(frameStart, frameEnd));
        }

        public void clearSplits()
        {
            splits.Clear();
        }

        public long getTimecode(long frameNumber)
        {
            return videoTrack.ConvertFrameNumberToTimecode(frameNumber);
        }

        private class MkvVideoTrack : VideoTrack
        {
            public readonly int TrackNumber;
            public readonly string VideoInfo;
            public readonly string EncodeSettings;

            private static int 
                findVideoTrack(FileInfo fileIn)
            {
                Regex trackSig = new Regex(@"Track ID (\d*?):[^(]*\((.*?)\)");
                int trackNum = -1;

                using (Process p = new Process())
                {
                    p.StartInfo.FileName = FilePaths.mkvmerge;
                    p.StartInfo.Arguments = "-i \"" + fileIn.FullName + "\"";
                    p.StartInfo.UseShellExecute = false;
                    p.StartInfo.RedirectStandardOutput = true;
                    p.StartInfo.CreateNoWindow = true;

                    p.Start();
                    string output = p.StandardOutput.ReadToEnd();
                    p.WaitForExit();
                    if (p.ExitCode == 0)
                    {
                        foreach (Match m in trackSig.Matches(output))
                        {
                            string mime = m.Groups[2].ToString();
                            if (mime.StartsWith("V_"))
                            {
                                if (trackNum < 0 && mime.Equals("V_MPEG4/ISO/AVC"))
                                {
                                    trackNum = int.Parse(m.Groups[1].ToString());
                                }
                                else
                                {
                                    throw new System.NotSupportedException(
                                        "File has more than one video stream");
                                }

                            }
                        }
                    } //End output parsing
                } //End process execution

                if (trackNum >= 0)
                {
                    return trackNum;
                }

                throw new System.NotSupportedException(
                    "File has no AVC video stream.");
            }

            private static int optValue(string opt)
            {
                return Convert.ToInt32(opt.Substring(opt.IndexOf('=') + 1));
            }

            private static string getEncodeSettings(FileInfo fileIn)
            {
                MediaInfo info = new MediaInfo();
                StringBuilder b = new StringBuilder();

                if (info.Open(fileIn.FullName) == 0)
                    return null;
                string rawSettings = info.Get(StreamKind.Video, 0, "Encoded_Library_Settings");
                if (String.IsNullOrWhiteSpace(rawSettings))
                    return null;
                foreach (string opt in rawSettings.Split('/'))
                {
                    string val = opt.Trim();
                    if (val.StartsWith("bframes="))
                        b.Append(" --").Append(val.Replace("=", " "));
                    else if (val.StartsWith("ref="))
                        b.Append(" --").Append(val.Replace("=", " "));
                    else if (val.StartsWith("trellis=")) //needed?
                        b.Append(" --").Append(val.Replace("=", " "));
                    //else if (val.StartsWith("qp="))
                    //     b.Append(" --").Append(val.Replace("=", " "));
                    //else if (val.StartsWith("crf="))
                    //    b.Append(" --").Append(val.Replace("=", " "));
                    else if (val.StartsWith("b_pyramid="))
                    {
                        switch (optValue(val))
                        {
                            case 0: b.Append(" --b-pyramid none"); break;
                            case 1: b.Append(" --b-pyramid strict"); break;
                            default: b.Append(" --b-pyramid normal"); break;
                        }
                    }
                    else if (val.StartsWith("open_gop="))
                    {
                        if (optValue(val) != 0)
                            b.Append(" --open-gop");
                    }
                    else if (val.StartsWith("cabac="))
                    {
                        if (optValue(val) == 0)
                            b.Append(" --no-cabac");
                    }
                }

                return b.ToString();
            }

            //Throws: NotSupportedException
            public MkvVideoTrack(FileInfo fileIn, UpdateProgress progressCallback, IntPtr progressCustomData)
            {
                this.TrackNumber = findVideoTrack(fileIn);
                string colourspace;
                int bitdepth;

                //getTimecodes(fileIn, this.TrackNumber + 1, out colourspace, out bitdepth, out frames, out gops);
                try
                {
                    Ffms2.index(fileIn.FullName, out bitdepth, out colourspace,
                        out frames, out gops, progressCallback, progressCustomData);
                }
                catch (Ffms2.Ffms2Exception ex)
                {
                    if (ex.Cancelled)
                        throw new NotSupportedException("", ex);
                    throw new NotSupportedException("FFMS2 error: " + ex.Message, ex);
                }

                Colourspace = colourspace;
                Bitdepth = bitdepth;

                string time = FrameCount > 0 ? 
                    Timecodes.FormatTime(frames[frames.Length - 1].timecode) : "0";

                EncodeSettings = getEncodeSettings(fileIn);

                VideoInfo = String.Format(
                    "Video Track ID: {0}\r\n" +
                    "Detected colourspace/bitdepth: {1}, {2}-bit\r\n" +
                    "Frame count: {3} (Duration {4})\r\n" +
                    "Keyframe count: {5}\r\n" + 
                    "Detected compat-encode: {6}\r\n",
                    TrackNumber, Colourspace, Bitdepth,
                    FrameCount, time, gops.Length, EncodeSettings);
            }
        } //MkvVideoTrack
    } //MkvHandler
}
